/**
 * Copyright (c), 上海沃恩信息科技有限公司.
 * All rights reserved.
 * Licensed under BSD (https://www.pittypat.work/bsd.html).
 */

package pittypat.web;

import pittypat.AuthorityService;
import pittypat.DynamicModel;
import pittypat.FunctionArgs;
import pittypat.FunctionMeta;
import pittypat.FunctionModel;
import pittypat.IAsyncProgress;
import pittypat.IAuthorityService;
import pittypat.IJson;
import pittypat.IdentifierHelper;
import pittypat.ModulePresentation;
import pittypat.ParameterMeta;
import pittypat.ParameterRole;
import pittypat.Permission;
import pittypat.SystemTokenProvider;
import pittypat.configuration.AppConfig;
import pittypat.configuration.ConfigException;
import pittypat.configuration.FunctionConfig;
import pittypat.configuration.ModuleConfig;
import pittypat.configuration.PittypatConfig;

import java.beans.PropertyVetoException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

/**
 * 提供请求中目标功能的相关信息。
 */
final class FunctionInfo {
	/**
	 * 功能信息保存在请求对象的属性列表中使用的属性名称。
	 */
	static final String ATTRIBUTE_NAME = FunctionInfo.class.getName();

	// 最小可以上传文件的尺寸：1KB。该值并不表示不能上传小于 1KB 的文件，而是表示可以配置的 maxUploadSize 最小为 1KB
	private static final long MIN_UPLOAD_SIZE = 1024L;

	// 最大可以上传文件的尺寸：2GB - 1
	private static final long MAX_UPLOAD_SIZE = 2147483647L;

	/**
	 * 功能隶属模块的信息。
	 */
	ModuleInfo moduleInfo;

	/**
	 * 功能的元数据信息。
	 */
	FunctionMeta meta;

	/**
	 * 缓存的与功能相关的类型，包括 FunctionArgs 类型和 FunctionModel&lt;FunctionArgs&gt; 的类型。
	 */
	FunctionTypes types;

	/**
	 * 功能在模块 Api 类中对应的方法。用于通过发射调用执行该方法。
	 */
	Method method;

	/**
	 * 功能的 FunctionModel 对象实例。
	 */
	FunctionModel<?> funcModel;

	/**
	 * 获取功能的路径，格式为：{开发者代码}.{应用标识符}.{模块标识符}.{功能标识符}。
	 */
	String path;

	/**
	 * 使用模块信息、类型、元数据信息、目标方法、功能模型对象以及路径初始化 FunctionInfo 类的新实例。
	 * 
	 * @param path
	 *            功能的完整路径。
	 * @param moduleInfo
	 *            模块信息。
	 * @param types
	 *            类型信息。
	 * @param meta
	 *            元数据信息。
	 * @param method
	 *            目标方法。
	 * @param funcModel
	 *            功能模型对象。
	 */
	public FunctionInfo(String path, ModuleInfo moduleInfo, FunctionTypes types, FunctionMeta meta, Method method,
			FunctionModel<?> funcModel) {
		this.moduleInfo = moduleInfo;
		this.meta = meta;
		this.types = types;
		this.method = method;
		this.funcModel = funcModel;
		this.path = path;
	}

	/**
	 * 获取当前功能对应的模型对象。其中包含了操作是否成功的指示。
	 * 
	 * @return 当前功能对应的模型对象。
	 */
	public FunctionModel<?> getModel() {
		return this.funcModel;
	}

	/**
	 * 获取允许上传的文件的最大尺寸，单位：字节，介于 1024 与 2147483647 之间。
	 * 
	 * @return 允许上传的文件的最大尺寸。
	 */
	public long getMaxUploadSize() {
		return Math.min(Math.max(this.meta.getConfig().getMaxUploadSize() * 1024L, MIN_UPLOAD_SIZE), MAX_UPLOAD_SIZE);
	}

	/**
	 * 从请求的查询字符串字段列表或表单字段列表中查找指定字段的值，并将值转换为目标类型。
	 * 
	 * @param param
	 *            参数的元数据信息。
	 * @param query
	 *            请求中包含的查询字符串字段列表。
	 * @param form
	 *            请求表单中的字段列表，可能来自表单或者 WebSocket。
	 * @return 目标参数的值，已经转换为元数据信息中对应的参数类型。
	 */
	private static Object getParamValue(ParameterMeta param, Map<String, String[]> query, MultiMap<String> form) {
		// 字段名称
		String name = param.getLowerIdentifier();
		// 从查询字符串或表单中找到的值
		String s = "";

		// 首先查找查询字符串
		if (query != null && query.size() > 0) {
			for (Map.Entry<String, String[]> e : query.entrySet()) {
				// 参数名称不区分大小写
				if (name.equalsIgnoreCase(e.getKey())) {
					for (String sv : e.getValue()) {
						if (sv != null && sv.length() > 0) {
							s = sv;
							break;
						}
					}

					if (s != null && s.length() > 0) {
						break;
					}
				}
			}
		}

		// 查找表单
		if ((s == null || s.length() == 0) && form != null && form.size() > 0) {
			for (Map.Entry<String, List<String>> e : form.entrySet()) {
				// 参数名称不区分大小写
				if (name.equalsIgnoreCase(e.getKey())) {
					for (String sv : e.getValue()) {
						if (sv != null && sv.length() > 0) {
							s = sv;
							break;
						}
					}

					if (s != null && s.length() > 0) {
						break;
					}
				}
			}
		}

		// 将字符串转换为目标类型
		return param.valueFromString(s);
	}

	/**
	 * 为功能创建环境变量参数，也就是传递给存储过程的 _context 的参数的值。
	 * 
	 * @param lang
	 *            Accept-Language 中定义的首选语言。
	 * @param ip
	 *            客户端 IP 地址，如果是通过 WebSocket 请求，则可能无法获取该地址。
	 * @return 一个 DynamicModel 对象，被转换为 JSON 字符串后传递给数据服务。
	 */
	public static DynamicModel createFunctionContext(String lang, String ip) {
		// 客户端可接受的语言列表，比如：zh-CN,zh-TW;q=0.8,zh-HK;q=0.6,zh-MO;q=0.4,zh-SG;q=0.2
		// 语言总是只取列表中的第一个
		if (lang != null && lang.length() > 0) {
			String[] v = lang.split(",");
			String[] vv = null;
			double q = 0d;

			lang = "";
			for (String vi : v) {
				vv = vi.split(";");
				if (vv.length == 1 && vv[0] != null && vv[0].length() > 0) {
					// 上例中的 zh-CN
					lang = vv[0];
					break;
				}

				if (vv.length > 1 && vv[0] != null && vv[0].length() > 0 && vv[1] != null && vv[1].length() > 2
						&& vv[1].startsWith("q=")) {
					try {
						q = Double.parseDouble(vv[1].substring(2));
					} catch (NumberFormatException e) {
						q = 0d;
					}

					if (q >= 1d) {
						lang = vv[0];
						break;
					}
				}
			}
		}

		// IP 地址
		DynamicModel dm = new DynamicModel();
		dm.set("lang", lang == null ? "" : lang);
		dm.set("ip", ip == null ? "" : ip);
		return dm;
	}

	/**
	 * 从请求的查询字段列表和表单字段列表中读取功能的参数值，并执行目标功能方法。
	 * 
	 * @param query
	 *            查询字符串字段列表。
	 * @param form
	 *            表单字段列表，可能来自表单或者 WebSocket。
	 * @param progress
	 *            向客户端汇报操作的进度。
	 * 
	 * @throws InvocationTargetException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 * @throws SecurityException
	 * @throws NoSuchFieldException
	 */
	public IJson invoke(Map<String, String[]> query, MultiMap<String> form, IAsyncProgress progress)
			throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchFieldException,
			SecurityException, InstantiationException {
		if (this.method == null || this.moduleInfo == null || this.funcModel == null) {
			// 这是一个严重错误，说属于 pittypat 核心编码错误
			throw new IllegalStateException("没有设置要执行的功能方法或功能参数。");
		}

		// 设置 FunctionModel 对象属性，用户属性已经在 FunctionInfo.demand 方法中设置

		// FunctionModel 的功能路径
		this.funcModel.setPath(this.path);

		// FunctionModel 所有的参数值
		FunctionArgs args = this.funcModel.getArgs();
		for (ParameterMeta param : this.meta.getParameters()) {
			if (param.isIn()) {
				if (param.getRole() == ParameterRole.USER_ID) {
					// 用户 Id 从当前用户信息中获取
					args.setParameterValue(param.getIdentifier(), this.funcModel.getUser().getId());
				} else {
					// 其他参数值从查询字符串或表单字段中获取，并进行类型转换
					args.setParameterValue(param.getIdentifier(), getParamValue(param, query, form));
				}
			}
		}

		// 执行目标功能方法
		return (IJson) this.method.invoke(this.moduleInfo.type.newInstance(), this.funcModel,
				progress == null ? WebAsyncProgress.DEFAULT : progress);
	}

	/**
	 * 检查当前用户对目标功能的权限。
	 * 
	 * @param userToken
	 *            当前用户的令牌。
	 * 
	 * @return 对用户进行权限检查的结果。
	 * @throws IllegalAccessException
	 * @throws InstantiationException
	 * @throws ClassNotFoundException
	 * @throws SecurityException
	 * @throws NoSuchFieldException
	 * @throws IllegalArgumentException
	 * @throws PropertyVetoException
	 * @throws ConfigException
	 * @throws SQLException
	 * @throws NoSuchMethodException
	 * @throws InvocationTargetException
	 * @throws UnsupportedEncodingException
	 */
	public Permission demand(String userToken)
			throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException,
			NoSuchFieldException, SecurityException, UnsupportedEncodingException, InvocationTargetException,
			NoSuchMethodException, SQLException, ConfigException, PropertyVetoException {
		// 初始化权限检查结果为未授权的匿名用户
		Permission permission = Permission.ANONYMOUS_UNAUTHORIZED;

		if (userToken == null || userToken.length() == 0) {
			// 没有找到用户令牌，认为当前用户是匿名用户
			// 检查目标模块是否允许匿名用户访问，如果允许匿名用户访问，则返回 ANONYMOUS_AUTHORIZED，否则返回
			// ANONYMOUS_UNAUTHORIZED。
			permission = this.moduleInfo.annotation.allowAnonymous() ? Permission.ANONYMOUS_AUTHORIZED
					: Permission.ANONYMOUS_UNAUTHORIZED;
		} else {
			// 获取当前验证服务
			IAuthorityService authService = AuthorityService.getCurrent();

			if (authService == null) {
				// 没有提供验证服务
				throw new IllegalStateException("没有配置用户权限验证安全服务，请检查配置文件的 authenticationService 配置项目。");
			}

			// 在目标验证服务上根据用户令牌和功能路径执行权限检查
			permission = authService.demand(SystemTokenProvider.getCurrent().getSystemToken(), userToken, this.path,
					WebAsyncProgress.DEFAULT);

			if (permission == null) {
				// 验证服务没有返回权限检查结果，认为当前用户是匿名用户
				// 检查目标模块是否允许匿名用户访问，如果允许匿名用户访问，则返回 ANONYMOUS_AUTHORIZED，否则返回
				// ANONYMOUS_UNAUTHORIZED。
				permission = this.moduleInfo.annotation.allowAnonymous() ? Permission.ANONYMOUS_AUTHORIZED
						: Permission.ANONYMOUS_UNAUTHORIZED;
			}
		}

		if (this.funcModel != null) {
			// 设置 FunctionModel 的当前用户对象。如果正在上传文件，则 funcModel 是 null 的。
			this.funcModel.setUser(permission.getUser());
		}

		// 返回权限检查的结果
		return permission;
	}

	/**
	 * 根据请求路径查找目标功能并创建一个代表功能信息的 FunctionInfo 对象。
	 * 
	 * @param names
	 *            包含了请求路径、开发者代码、应用标识符、模块标识符和功能标识符的数组。
	 * @param createModel
	 *            指示是否应该创建 FunctionModel 对象。在上传文件时，是不需要创建 FunctionModel 对象的。
	 * @return 一个 FunctionInfo 对象，如果没有找到目标功能，则返回 null。
	 * @throws ClassNotFoundException
	 * @throws IllegalAccessException
	 * @throws InstantiationException
	 * @throws SecurityException
	 * @throws NoSuchFieldException
	 * @throws InvocationTargetException
	 * @throws IllegalArgumentException
	 */
	public static FunctionInfo create(String[] names, boolean createModel)
			throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException,
			SecurityException, IllegalArgumentException, InvocationTargetException {
		// names 数组中至少需要包含：完整路径、开发者代码、应用标识符、模块标识符和功能标识符五个部分
		if (names.length < 5) {
			return null;
		}

		// 检查开发者代码、应用标识符、模块标识符和功能标识符是否是有效的标识符
		if (!IdentifierHelper.isIdentifier(names[1]) || !IdentifierHelper.isIdentifier(names[2])
				|| !IdentifierHelper.isIdentifier(names[3]) || !IdentifierHelper.isIdentifier(names[4])) {
			// 其中一个标识符不是有效的标识符，此时不会找到相应的功能
			return null;
		}

		// 查找模块信息
		ModuleInfo moduleInfo = ModuleInfo.find(names[1], names[2], names[3]);
		if (moduleInfo == null) {
			// 没有找到模块信息
			return null;
		}

		// 检查模块是否允许对外公开
		if (moduleInfo.annotation.presentation() == ModulePresentation.INTERNAL) {
			// 模块不能对外公开访问
			return null;
		}

		// 检查模块当前是否正在维护
		if (moduleInfo.isMaintenance) {
			// 返回一个正在维护的消息，不能返回 null
			return moduleInfo.maintenanceFunctionInfo;
		}

		// 查找当前路径对应的功能方法，通过缓存减少反射带来的性能损失
		Method method = getFunctionMethod(names[0], names[4], moduleInfo.type);

		if (method == null) {
			// 没有找到当前路径对应的功能方法
			return null;
		}

		// 功能完整路径
		String funcPath = String.format("%s.%s.%s.%s", names[1], names[2], names[3], names[4]);

		// 获取功能的类型信息，包括 FunctionArgs 和 FunctionModel 两个类型，通过缓存减少反射带来的性能损失
		FunctionTypes types = getTypes(names[1], names[2], names[3], names[4], funcPath);
		if (types == null) {
			// 没有找到功能的类型信息
			return null;
		}

		// 查找功能的元数据信息
		FunctionMeta meta = null;
		try {
			meta = FunctionMeta.find(types.argsType);
		} catch (ConfigException e) {
			// 没有找到功能的元数据信息
			return null;
		}

		if (meta == null) {
			// 没有找到功能的元数据信息
			return null;
		}

		// 创建执行目标功能需要的 FunctionModel 对象，如果当前正在上传文件，那么 createModel 为
		// false，此时不需要创建 FunctionModel 对象
		FunctionModel<?> funcModel = null;
		if (createModel) {
			// 创建 FunctionModel 的对象实例
			funcModel = (FunctionModel<?>) types.funcModelType.getConstructors()[0].newInstance(types.argsType);
		}

		// 创建新的 FunctionInfo 对象
		return new FunctionInfo(funcPath, moduleInfo, types, meta, method, funcModel);
	}

	// 功能路径对应的功能方法的缓存，字典键为功能的路径，字典值为方法
	private static final TreeMap<String, Method> functionMethods = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
	// 对功能方法字典进行同步访问的对象
	private static final Object functionMethodsLock = new Object();

	/**
	 * 根据功能路径、功能标识符和模块的 Api 类型查找功能在 Api 类中映射到的 Method。
	 * 
	 * @param funcPath
	 *            功能的完整路径。
	 * @param funcIdentifier
	 *            功能的标识符。
	 * @param apiType
	 *            模块的 Api 类型。
	 * @return 功能在模块 Api 类中映射到的 Method。
	 */
	private static Method getFunctionMethod(String funcPath, String funcIdentifier, Class<?> apiType) {
		synchronized (functionMethodsLock) {
			// 首先从缓存中查找
			Method method = functionMethods.get(funcPath);
			if (method == null) {
				// Api 类型上的所有方法的列表
				Method[] methods = apiType.getDeclaredMethods();
				if (methods == null || methods.length == 0) {
					// 没有找到 Api 的功能方法
					return null;
				}

				// 查找当前路径对应的功能方法
				for (Method m : methods) {
					// 方法名称的比较不区分大小写
					if (m.getName().equalsIgnoreCase(funcIdentifier)) {
						method = m;
						break;
					}
				}

				// 添加到缓存中
				functionMethods.put(funcPath, method);
			}

			return method;
		}
	}

	/**
	 * 缓存中的功能相关的类型，包括 FunctionArgs 和 FunctionModel 两个类型。
	 */
	private static final class FunctionTypes {
		Class<?> argsType;
		Class<?> funcModelType;

		/**
		 * 使用目标 FunctionArgs 和 FunctionModel 类型初始化 FunctionTypes 的新实例。
		 * 
		 * @param argsType
		 *            FunctionArgs 类型。
		 * @param funcModelType
		 *            FunctionModel 类型。
		 */
		private FunctionTypes(Class<?> argsType, Class<?> funcModelType) {
			this.argsType = argsType;
			this.funcModelType = funcModelType;
		}
	}

	// 缓存字典，字典的键为功能的完整路径，字典值为 FunctionTypes 对象实例
	private static final TreeMap<String, FunctionTypes> cachedTypes = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
	// 对功能类型字典进行同步访问的对象
	private static final Object cachedTypesLock = new Object();

	/**
	 * 获取指定功能路径的类型信息。
	 * 
	 * @param developerCode
	 *            开发者代码。
	 * @param appIdentifier
	 *            App 标识符。
	 * @param moduleIdentifier
	 *            模块标识符。
	 * @param functionIdentifier
	 *            功能标识符。
	 * @param path
	 *            功能的路径。
	 * @return 功能的类型信息。
	 */
	private static FunctionTypes getTypes(String developerCode, String appIdentifier, String moduleIdentifier,
			String functionIdentifier, String path) {
		synchronized (cachedTypesLock) {
			// 首先从缓存中查找
			FunctionTypes types = cachedTypes.get(path);
			if (types == null) {
				// 加载功能的 FunctionArgs 类型
				Class<?> argsType = findArgsType(developerCode, appIdentifier, moduleIdentifier, functionIdentifier);

				if (argsType == null) {
					return null;
				}

				// 使用 FunctionArgs 类型和 FunctionModel 类型初始化一个 FunctionTypes 对象实例
				types = new FunctionTypes(argsType, FunctionModel.class);

				// 添加到缓存中
				cachedTypes.put(path, types);
			}

			return types;
		}
	}

	/**
	 * 查找指定路径的功能的 FunctionArgs 类型。
	 * 
	 * @param developerCode
	 *            开发者代码。
	 * @param appIdentifier
	 *            App 标识符。
	 * @param moduleIdentifier
	 *            模块标识符。
	 * @param functionIdentifier
	 *            功能标识符。
	 * @return 功能的 FunctionArgs 类型，如果没有找到，则返回 null。
	 */
	private static Class<?> findArgsType(String developerCode, String appIdentifier, String moduleIdentifier,
			String functionIdentifier) {
		// 查找目标应用的配置信息
		AppConfig appConfig = PittypatConfig.getCurrent().getAppConfig(developerCode, appIdentifier);
		if (appConfig == null) {
			return null;
		}

		// 查找目标模块的配置信息
		ModuleConfig moduleConfig = appConfig.getModuleConfig(moduleIdentifier);
		if (moduleConfig == null) {
			return null;
		}

		// 查找目标功能的配置信息
		FunctionConfig funcConfig = moduleConfig.getFunctionConfig(functionIdentifier);
		if (funcConfig == null) {
			return null;
		}

		try {
			// 功能的 FunctionArgs 的类型名称，并查找相关类型，此时功能标识符必须与 FunctionArgs
			// 的继承类的名称严格一致（包括大小写），否则将找不到目标类型
			return Class.forName(String.format("com.%s.%s.%s.args.%s", developerCode.toLowerCase(),
					appIdentifier.toLowerCase(), moduleIdentifier.toLowerCase(), funcConfig.getIdentifier()));
		} catch (ClassNotFoundException e) {
			return null;
		}
	}
}
